package main

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"log"
	"time"

	_ "github.com/go-sql-driver/mysql"

	"block_save_Db/BlockUtils"
)

type Block struct {
	Id          int64        `gorm:"column:id"`
	Slot        int64        `gorm:"column:slot"`
	BlockHeight int64        `gorm:"column:block_height"`
	BlockTime   time.Time    `gorm:"column:block_time"`
	Status      int64        `gorm:"column:status"`
	SolPrice    float64      `gorm:"column:sol_price"`
	CreatedAt   time.Time    `gorm:"column:created_at"`
	UpdatedAt   time.Time    `gorm:"column:updated_at"`
	DeletedAt   sql.NullTime `gorm:"column:deleted_at"`
	ErrMessage  string       `gorm:"column:err_message"`
}

type BlockResponse struct {
	JSONRPC string `json:"jsonrpc"`
	Result  struct {
		BlockHeight int64  `json:blockHeight`
		BlockTime   int64  `json:blockTime`
		Blockhash   string `json:blockhash`
	} `json:"result"`
	Error struct {
		Code    int    `json:"code"`
		Message string `json:"message"`
	} `json:error`
}

const (
	BlockProcessed = 1
	BlockFailed    = 2
	// BlockSkipped GetBlock err:{"code":-32007,"message":"Slot 311350484 was skipped, or missing due to ledger jump to recent snapshot","data":null}
	BlockSkipped = 3
)

/*
*
根据区块号查询
如果查到，就更新
否则，就插入到数据库
*/
func saveOrUpdate(ctx context.Context, db *sql.DB, block *Block) error {
	var exists bool
	err := db.QueryRowContext(ctx, "select exists(select 1 from block where slot = ?)", block.Slot).Scan(&exists)
	if err != nil {
		return fmt.Errorf("error check if exists:%v", err)
	}
	if exists {
		_, err := db.ExecContext(ctx, "update block set block_height = ?,block_time = ?,status = ?,sol_price = ?,err_message = ?,updated_at = now() where slot = ?",
			block.BlockHeight, block.BlockTime, block.Status, block.SolPrice, block.ErrMessage, block.Slot)
		if err != nil {
			return fmt.Errorf("error update block:%v", err)
		}
		fmt.Printf("update success with slot:%v", block.Slot)
	} else {
		_, err := db.ExecContext(ctx, "insert into block(slot,block_height,block_time,status,sol_price,err_message,created_at,updated_at) values (?,?,?,?,?,?,now(),now())",
			block.Slot, block.BlockHeight, block.BlockTime, block.Status, block.SolPrice, block.ErrMessage)
		if err != nil {
			return fmt.Errorf("error insert block:%v", err)
		}
		fmt.Printf("insert success with slot:%v", block.Slot)
	}

	return nil
}

/*
**
根据区块号查询区块信息
*/
func getBlockBySlot(db *sql.DB, slot int64) Block {
	var storedBlock Block

	query := "select id,slot,block_height,block_time,status,sol_price,created_at,updated_at,deleted_at,err_message from block where slot = ?"
	err := db.QueryRow(query, slot).Scan(
		&storedBlock.Id,
		&storedBlock.Slot,
		&storedBlock.BlockHeight,
		&storedBlock.BlockTime,
		&storedBlock.Status,
		&storedBlock.SolPrice,
		&storedBlock.CreatedAt,
		&storedBlock.UpdatedAt,
		&storedBlock.DeletedAt,
		&storedBlock.ErrMessage,
	)
	if err != nil {
		log.Fatal(err)
	}

	return storedBlock
}

func main() {

	datasource := "root:root@tcp(127.0.0.1:3306)/test?parseTime=true"
	db, err := sql.Open("mysql", datasource)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	err = db.Ping()
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println("successfully connected")

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	saveBlockToDB(ctx, db, 378067747)

}

func saveBlockToDB(ctx context.Context, db *sql.DB, slot int64) {
	result := BlockUtils.GetBlock(slot)

	block := &Block{
		Slot:       slot,
		BlockTime:  time.Now(),
		SolPrice:   0.0,
		Status:     BlockFailed,
		ErrMessage: "",
	}

	var blockResponse BlockResponse

	err := json.Unmarshal([]byte(result), &blockResponse)
	if err != nil {
		block.ErrMessage = fmt.Sprintf("转换错误%v", err)
	}

	if blockResponse.Error.Code != 0 {
		block.ErrMessage = fmt.Sprintf("Api error : %s", blockResponse.Error.Message)
	}
	block.Status = BlockProcessed
	block.BlockHeight = blockResponse.Result.BlockHeight
	blockTime := time.Unix(blockResponse.Result.BlockTime, 0)
	block.BlockTime = blockTime

	saveOrUpdate(ctx, db, block)
}
